/*-------------------------------------------------------------------------
 *
 * main.c
 *	  postgres可执行文件的Stub main()例程。
 *
 * 这做了一些对任何postgres实例（postmaster、独立后端、独立引导过程或单独执行的postmaster子进程）的基本启动任务，然后分发到正确的FooMain()例程。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/main/main.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <unistd.h>

#if defined(WIN32)
#include <crtdbg.h>
#endif

#if defined(__NetBSD__)
#include <sys/param.h>
#endif

#if defined(_M_AMD64) && _MSC_VER == 1800
#include <math.h>
#include <versionhelpers.h>
#endif

#include "bootstrap/bootstrap.h"
#include "common/username.h"
#include "miscadmin.h"
#include "port/atomics.h"
#include "postmaster/postmaster.h"
#include "storage/spin.h"
#include "tcop/tcopprot.h"
#include "utils/help_config.h"
#include "utils/memutils.h"
#include "utils/pg_locale.h"
#include "utils/ps_status.h"


const char *progname;


static void fc_startup_hacks(const char *fc_progname);
static void fc_init_locale(const char *fc_categoryname, int fc_category, const char *fc_locale);
static void fc_help(const char *fc_progname);
static void fc_check_root(const char *fc_progname);


/*
 * 任何 PostgreSQL 服务器进程从这里开始执行。
 */
int main(int fc_argc, char *fc_argv[])
{
	bool		fc_do_check_root = true;

	/*
	 * 如果在当前平台上支持，请设置一个处理程序，以便在
	 * 后端/主进程因致命信号或异常崩溃时调用。
	 */
#if defined(WIN32) && defined(HAVE_MINIDUMP_TYPE)
	pgwin32_install_crashdump_handler();
#endif

	progname = get_progname(fc_argv[0]);

	/*
	 * 平台特定的启动黑客
	 */
	fc_startup_hacks(progname);

	/*
	 * 记住最初给定的 argv[] 数组的物理位置，以便可能
	 * 由 ps 显示使用。在某些平台上，必须覆盖 argv[]
	 * 存储以设置进程标题，以便在 ps 中使用。
	 * 在这种情况下，save_ps_display_args 会创建并返回 argv[]
	 * 数组的新副本。
	 *
	 * save_ps_display_args 也可能会移动环境字符串以腾出
	 * 额外空间。因此应该尽早在启动期间执行此操作，
	 * 以避免与可能保存 getenv() 返回指针的代码发生混淆。
	 */
	fc_argv = save_ps_display_args(fc_argc, fc_argv);

	/*
	 * 启动基本子系统：错误和内存管理
	 *
	 * 此代码之后可以使用 elog/ereport，尽管消息的本地化
	 * 可能不会立即生效，并且消息不会发送到其他地方，
	 * 直到加载 GUC 设置。
	 */
	MyProcPid = getpid();
	MemoryContextInit();

	/*
	 * 设置区域信息
	 */
	set_pglocale_pgservice(fc_argv[0], PG_TEXTDOMAIN("postgres"));

	/*
	 * 在主进程中，吸收 LC_COLLATE 和 LC_CTYPE 的环境值。
	 * 各个后端稍后将根据 pg_database 中的设置更改这些值，
	 * 但主进程无法执行此操作。如果我们将这些值设置为
	 * “C”，那么在主进程中消息本地化可能会效果不好。
	 */
	fc_init_locale("LC_COLLATE", LC_COLLATE, "");
	fc_init_locale("LC_CTYPE", LC_CTYPE, "");

	/*
	 * LC_MESSAGES 会在 GUC 选项处理期间稍后设置，但我们在这里设置
	 * 它以允许启动错误消息本地化。
	 */
#ifdef LC_MESSAGES
	fc_init_locale("LC_MESSAGES", LC_MESSAGES, "");
#endif

	/*
	 * 我们始终将这些设置为 "C"，除非在 pg_locale.c 中临时使用；有关解释，请参阅该文件。
	 */
	fc_init_locale("LC_MONETARY", LC_MONETARY, "C");
	fc_init_locale("LC_NUMERIC", LC_NUMERIC, "C");
	fc_init_locale("LC_TIME", LC_TIME, "C");

	/*
	 * 现在我们已经从区域环境中吸取了尽可能多的内容，移除任何 LC_ALL 设置，以便 pg_perm_setlocale 安装的环境变量具有强制性。
	 */
	unsetenv("LC_ALL");

	check_strxfrm_bug();

	/*
	 * 在做其他事情之前先捕获标准选项，特别是在我们坚持不成为 root 之前。
	 */
	if (fc_argc > 1)
	{
		if (strcmp(fc_argv[1], "--help") == 0 || strcmp(fc_argv[1], "-?") == 0)
		{
			fc_help(progname);
			exit(0);
		}
		if (strcmp(fc_argv[1], "--version") == 0 || strcmp(fc_argv[1], "-V") == 0)
		{
			fputs(PG_BACKEND_VERSIONSTR, stdout);
			exit(0);
		}

		/*
		 * 除上述外，我们允许 root 调用 "--describe-config" 和 "-C var"。这是相对安全的，因为这些都是只读活动。-C 情况很重要，因为 pg_ctl 可能会试图在仍持有管理员权限的 Windows 上调用它。请注意，虽然 -C 通常可以在任何 argv 位置，但如果您想绕过 root 检查，必须将其放在第一位。这减少了我们可能会误解其他模式的 -C 开关为 postmaster/postgres 选项的风险。
		 */
		if (strcmp(fc_argv[1], "--describe-config") == 0)
			fc_do_check_root = false;
		else if (fc_argc > 2 && strcmp(fc_argv[1], "-C") == 0)
			fc_do_check_root = false;
	}

	/*
	 * 确保我们不是以 root 身份运行，除非对所选选项来说是安全的。
	 */
	if (fc_do_check_root)
		fc_check_root(progname);

	/*
	 * 根据第一个参数派遣到各种子程序之一。
	 */

	if (fc_argc > 1 && strcmp(fc_argv[1], "--check") == 0)
		BootstrapModeMain(fc_argc, fc_argv, true);
	else if (fc_argc > 1 && strcmp(fc_argv[1], "--boot") == 0)
		BootstrapModeMain(fc_argc, fc_argv, false);
#ifdef EXEC_BACKEND
	else if (fc_argc > 1 && strncmp(fc_argv[1], "--fork", 6) == 0)
		SubPostmasterMain(fc_argc, fc_argv);
#endif
	else if (fc_argc > 1 && strcmp(fc_argv[1], "--describe-config") == 0)
		GucInfoMain();
	else if (fc_argc > 1 && strcmp(fc_argv[1], "--single") == 0)
		PostgresSingleUserMain(fc_argc, fc_argv,
							   strdup(get_user_name_or_exit(progname)));
	else
		PostmasterMain(fc_argc, fc_argv);
	/* 上述函数不应返回 */
	abort();
}



/*
 * 在这里放置特定于平台的启动修补程序。这是放置必须在任何新的服务器进程启动时尽早执行的代码的正确位置。请注意，当后端或子引导进程被分叉时，此代码不会被执行，除非我们处于分叉/执行环境（即 EXEC_BACKEND 被定义）。
 *
 * XXX 这里需要代码证明了相关平台过于无能，无法提供无助的标准 C 执行环境。如果可以的话，避免在这里添加更多内容。
 */
static void fc_startup_hacks(const char *fc_progname)
{
	/*
	 * Windows 特定的执行环境黑客。
	 */
#ifdef WIN32
	{
		WSADATA		wsaData;
		int			err;

		/* 默认情况下使输出流无缓冲 */
		setvbuf(stdout, NULL, _IONBF, 0);
		setvbuf(stderr, NULL, _IONBF, 0);

		/* 准备 Winsock */
		err = WSAStartup(MAKEWORD(2, 2), &wsaData);
		if (err != 0)
		{
			write_stderr("%s: WSAStartup failed: %d\n",
						 progname, err);
			exit(1);
		}

		/*
		 * 默认情况下，abort() 仅在 *非* 调试构建中生成崩溃转储。由于我们的 Assert() / ExceptionalCondition() 使用 abort()，因此保留默认设置将使调试更加困难。
		 *
		 * MINGW 自己的 C 运行时没有 _set_abort_behavior()。当以 mingw 目标 Microsoft 的 UCRT 时，它从不链接库的调试版本，因此也不需要调用 _set_abort_behavior()。
		 */
#if !defined(__MINGW32__) && !defined(__MINGW64__)
		_set_abort_behavior(_CALL_REPORTFAULT | _WRITE_ABORT_MSG,
							_CALL_REPORTFAULT | _WRITE_ABORT_MSG);
#endif							/* !defined(__MINGW32__) &&
								 * !defined(__MINGW64__) */

		/*
		 * SEM_FAILCRITICALERRORS 会导致更多错误被报告给调用者。
		 *
		 * 我们过去也指定 SEM_NOGPFAULTERRORBOX，但那样会阻止 Windows 崩溃报告工作。这包括注册的即时调试器，使得在 Windows 上调试问题变得不必要地困难。现在我们尝试在下面单独禁用弹出窗口的源（请注意，SEM_NOGPFAULTERRORBOX 实际上并没有阻止所有此类弹出窗口的来源）。
		 */
		SetErrorMode(SEM_FAILCRITICALERRORS);

		/*
		 * 在 stderr 上显示错误，而不是弹出框（请注意，这不会影响来自 C 运行时的错误，见下文）。
		 */
		_set_error_mode(_OUT_TO_STDERR);

		/*
		 * 在 DEBUG 构建中，错误（包括断言、C 运行时错误）通过 _CrtDbgReport 报告。默认情况下，这些错误通过弹出窗口显示（即使使用 NOGPFAULTERRORBOX），妨碍了前进。相反，在 stderr（和调试器）上报告此类错误。这是 C 运行时特定的，因此上述咒语不足以抑制这些弹出窗口。
		 */
		_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
		_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
		_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
		_CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
		_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG);
		_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);

#if defined(_M_AMD64) && _MSC_VER == 1800

		/*----------
		 * 在某些浮点操作中避免崩溃，如果我们为 x64 与 MS Visual Studio 2013 编译，并在运行 Windows 7/2008R2 SP1 之前的 AVX2 能够的 CPU 上。
		 *
		 * 参考： https://connect.microsoft.com/VisualStudio/feedback/details/811093/visual-studio-2013-rtm-c-x64-code-generation-bug-for-avx2-instructions
		 *----------
		 */
		if (!IsWindows7SP1OrGreater())
		{
			_set_FMA3_enable(0);
		}
#endif							/* defined(_M_AMD64) && _MSC_VER == 1800 */

	}
#endif							/* WIN32 */

	/*
	 * 初始化 dummy_spinlock，以防我们处于必须使用 pg_memory_barrier() 的后备实现的平台上。
	 */
	SpinLockInit(&dummy_spinlock);
}


/*
 * 为区域类别设置初始永久设置。如果失败，可能是因为环境中的 LC_foo=无效，请使用区域设置 C。如果即使这些都失败了，可能是因为内存不足，则整个启动也会失败。当返回时，我们保证有一个给定类别环境变量的设置。
 */
static void fc_init_locale(const char *fc_categoryname, int fc_category, const char *fc_locale)
{
	if (pg_perm_setlocale(fc_category, fc_locale) == NULL &&
		pg_perm_setlocale(fc_category, "C") == NULL)
		elog(FATAL, "could not adopt \"%s\" locale nor C locale for %s",
			 fc_locale, fc_categoryname);
}



/*
 * 帮助显示应与 PostmasterMain() 和 PostgresMain() 接受的选项匹配。
 *
 * XXX 在 Windows 上，这些消息的非 ASCII 本地化仅在控制台输出代码页覆盖必要字符时正确显示。write_console() 中发出的消息没有这种问题。
 */
static void fc_help(const char *fc_progname)
{
	printf(_("%s is the PostgreSQL server.\n\n"), fc_progname);
	printf(_("Usage:\n  %s [OPTION]...\n\n"), fc_progname);
	printf(_("Options:\n"));
	printf(_("  -B NBUFFERS        number of shared buffers\n"));
	printf(_("  -c NAME=VALUE      set run-time parameter\n"));
	printf(_("  -C NAME            print value of run-time parameter, then exit\n"));
	printf(_("  -d 1-5             debugging level\n"));
	printf(_("  -D DATADIR         database directory\n"));
	printf(_("  -e                 use European date input format (DMY)\n"));
	printf(_("  -F                 turn fsync off\n"));
	printf(_("  -h HOSTNAME        host name or IP address to listen on\n"));
	printf(_("  -i                 enable TCP/IP connections\n"));
	printf(_("  -k DIRECTORY       Unix-domain socket location\n"));
#ifdef USE_SSL
	printf(_("  -l                 enable SSL connections\n"));
#endif
	printf(_("  -N MAX-CONNECT     maximum number of allowed connections\n"));
	printf(_("  -p PORT            port number to listen on\n"));
	printf(_("  -s                 show statistics after each query\n"));
	printf(_("  -S WORK-MEM        set amount of memory for sorts (in kB)\n"));
	printf(_("  -V, --version      output version information, then exit\n"));
	printf(_("  --NAME=VALUE       set run-time parameter\n"));
	printf(_("  --describe-config  describe configuration parameters, then exit\n"));
	printf(_("  -?, --help         show this help, then exit\n"));

	printf(_("\nDeveloper options:\n"));
	printf(_("  -f s|i|o|b|t|n|m|h forbid use of some plan types\n"));
	printf(_("  -n                 do not reinitialize shared memory after abnormal exit\n"));
	printf(_("  -O                 allow system table structure changes\n"));
	printf(_("  -P                 disable system indexes\n"));
	printf(_("  -t pa|pl|ex        show timings after each query\n"));
	printf(_("  -T                 send SIGSTOP to all backend processes if one dies\n"));
	printf(_("  -W NUM             wait NUM seconds to allow attach from a debugger\n"));

	printf(_("\nOptions for single-user mode:\n"));
	printf(_("  --single           selects single-user mode (must be first argument)\n"));
	printf(_("  DBNAME             database name (defaults to user name)\n"));
	printf(_("  -d 0-5             override debugging level\n"));
	printf(_("  -E                 echo statement before execution\n"));
	printf(_("  -j                 do not use newline as interactive query delimiter\n"));
	printf(_("  -r FILENAME        send stdout and stderr to given file\n"));

	printf(_("\nOptions for bootstrapping mode:\n"));
	printf(_("  --boot             selects bootstrapping mode (must be first argument)\n"));
	printf(_("  --check            selects check mode (must be first argument)\n"));
	printf(_("  DBNAME             database name (mandatory argument in bootstrapping mode)\n"));
	printf(_("  -r FILENAME        send stdout and stderr to given file\n"));

	printf(_("\nPlease read the documentation for the complete list of run-time\n"
			 "configuration settings and how to set them on the command line or in\n"
			 "the configuration file.\n\n"
			 "Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
	printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
}



static void fc_check_root(const char *fc_progname)
{
#ifndef WIN32
	if (geteuid() == 0)
	{
		write_stderr("\"root\" execution of the PostgreSQL server is not permitted.\n"
					 "The server must be started under an unprivileged user ID to prevent\n"
					 "possible system security compromise.  See the documentation for\n"
					 "more information on how to properly start the server.\n");
		exit(1);
	}

	
/*
	 * 还确保实际和有效的用户ID相同。从根shell执行为setuid程序是一个安全隐患，因为在许多平台上，一个恶意的子例程可能会在实际用户ID为root时将用户ID设置回root。  （由于没有人实际上将postgres作为setuid程序使用，主动修复这种情况似乎费力不讨好；我们只是花点精力检查一下。）
	 */
	if (getuid() != geteuid())
	{
		write_stderr("%s: real and effective user IDs must match\n",
					 fc_progname);
		exit(1);
	}
#else							/* WIN32 */
	if (pgwin32_is_admin())
	{
		write_stderr("Execution of PostgreSQL by a user with administrative permissions is not\n"
					 "permitted.\n"
					 "The server must be started under an unprivileged user ID to prevent\n"
					 "possible system security compromises.  See the documentation for\n"
					 "more information on how to properly start the server.\n");
		exit(1);
	}
#endif							/* WIN32 */
}
